#include "TabTipSysServer2016.h"
#include "wndipc\IPCWndClient.h"
#include <memory>
#include <process.h>

#define DefaultTipSharedMemoryName  L"DefaultTIPSharedMemory"
#define TIPTSFDLL                   L"tiptsf.dll"

#define WM_TABTIP_MOUSELRBUTTONUP        WM_USER + 40
#define WM_TABTIP_INSTALLMOUSEHOOK       WM_USER + 41
#define WM_TABTIP_UNINSTALLMOUSEHOOK     WM_USER + 42

#define g_info printf

typedef __int64(__fastcall* InitializeCaretTracking)(bool bInit);
typedef int(__fastcall* AdviseHook)(HWND hwnd, bool a2);
typedef BOOL(__fastcall* StartCaretTracking)(const wchar_t* lpName, bool a2, bool a3);
typedef int(__fastcall* EndCaretTracking)(const wchar_t* lpName);
typedef int(__fastcall* UnadviseHook)(void);

HHOOK    CTabTipSysServer2016::m_hStaMouseHook = NULL;
CTabTipSysServer2016::CTabTipSysServer2016()
    : m_hShareMemoryFileMap(NULL)
    , m_hTipTsfModule(NULL)
    , m_bCaretTracking(false)
{
    memset(m_szBufTipSharedMemoryName, 0, sizeof(m_szBufTipSharedMemoryName));
}

CTabTipSysServer2016::~CTabTipSysServer2016()
{
}

BOOL CTabTipSysServer2016::InstallNotification()
{
    Initialize();
    InstallWndForIPC();
    InstallImmersiveFocusTracking();

    if (CTabTipSystemBase::ms_pIPCWndClient) // 安装mouse hook，必须在消息循环线程
    {
        if (S_OK != CTabTipSystemBase::ms_pIPCWndClient->send_message(WM_TABTIP_INSTALLMOUSEHOOK, 0, 0))
        {
            return FALSE;
        }
    }
    return TRUE;
}

BOOL CTabTipSysServer2016::UninstallNotification()
{
    UninstallImmersiveFocusTracking();
    Uninitialize();

    if (CTabTipSystemBase::ms_pIPCWndClient) // 卸载mouse hook
    {
        CTabTipSystemBase::ms_pIPCWndClient->send_message(WM_TABTIP_UNINSTALLMOUSEHOOK, 0, 0);
    }
    UninstallWndForIPC();
    return TRUE;
}

LRESULT CTabTipSysServer2016::OnProcessIPCMsg(UINT message, WPARAM wParam, LPARAM lParam)
{
    if (message == WM_TABTIP_INSTALLMOUSEHOOK)
    {
        m_hStaMouseHook = SetWindowsHookExW(
            WH_MOUSE_LL,
            (HOOKPROC)CTabTipSysServer2016::LowLevelMouseHookProc,
            GetModuleHandle(NULL),
            0);
        return S_OK;
    }
    else if(message == WM_TABTIP_UNINSTALLMOUSEHOOK)
    {
        if (m_hStaMouseHook)
        {
            UnhookWindowsHookEx(m_hStaMouseHook);
            m_hStaMouseHook = NULL;
        }
        return S_OK;
    }
    else if (message == WM_TABTIP_MOUSELRBUTTONUP)
    {
        std::shared_ptr<MSLLHOOKSTRUCT> pMouseHookStruct((PMSLLHOOKSTRUCT)lParam);
        HWND hPointWnd = WindowFromPoint(pMouseHookStruct->pt); //WindowFromPhysicalPoint(pMouseHookStruct->pt);
        HWND hDestWnd = (HWND)wParam;
        DWORD dwStartTime = GetTickCount();
        static HANDLE hProcess = OpenProcess(SYNCHRONIZE, FALSE, GetCurrentProcessId());// GetCurrentProcess();
        while (hDestWnd && (hDestWnd == hPointWnd))
        {
            // QS_ALLEVENTS可接受Post消息，An input, WM_TIMER, WM_PAINT, WM_HOTKEY, or posted message is in the queue.
            // 需要等待窗口移动稳定 不然WindowFromPoint获取的窗口会是窗口后面的窗口
            DWORD dwError = ::MsgWaitForMultipleObjects(1, &hProcess, TRUE, 5, QS_ALLINPUT);
            if (dwError == WAIT_FAILED || WAIT_OBJECT_0 == dwError || GetTickCount() - dwStartTime > 150)
                break;
            hPointWnd = WindowFromPoint(pMouseHookStruct->pt);
        }

        bool bShowInputWnd = false;
        do
        {
            if (hPointWnd)
            {
                RECT rtPtWnd;
                GetWindowRect(hPointWnd, &rtPtWnd);
                g_info("[TabTipSystemBase] Button Up and point %s HWnd, FocusEdit(0x%x) RECT:(%d,%d,%d,%d), Origin FocusEditPointHWnd:0x%X, mouse point:0x%X RECT:(%d,%d,%d,%d) point:(%d,%d) ",
                    (CTabTipSystemBase::m_hStaFocusEdit == hPointWnd || CTabTipSystemBase::m_hStaFocusEditPointHWnd == hPointWnd) ?
                    "Same" : "Different",
                    CTabTipSystemBase::m_hStaFocusEdit,
                    CTabTipSystemBase::m_rtStaFocusEdit.left, CTabTipSystemBase::m_rtStaFocusEdit.top,
                    CTabTipSystemBase::m_rtStaFocusEdit.right, CTabTipSystemBase::m_rtStaFocusEdit.bottom,
                    CTabTipSystemBase::m_hStaFocusEditPointHWnd, hPointWnd,
                    rtPtWnd.left, rtPtWnd.top, rtPtWnd.right, rtPtWnd.bottom, pMouseHookStruct->pt.x, pMouseHookStruct->pt.y);

                if (CTabTipSystemBase::m_hStaFocusEdit == hPointWnd
                    || CTabTipSystemBase::m_hStaFocusEditPointHWnd == hPointWnd)
                {
                    bShowInputWnd = true;
                    break;
                }
                else
                {
                    if (CTabTipSystemBase::m_hStaFocusEdit)
                    {
                        HWND hFocusAncestor = IsWindow(CTabTipSystemBase::m_hStaFocusAncestor) ?
                            CTabTipSystemBase::m_hStaFocusAncestor : GetAncestor(CTabTipSystemBase::m_hStaFocusEdit, GA_ROOT);
                        HWND hClickAncestor = ::GetAncestor(hPointWnd, GA_ROOT);
                        if (hFocusAncestor != hClickAncestor)
                        {
                            if (CTabTipSystemBase::m_bStaShowingInputWnd)
                            {
                                RECT rtPointWnd, rtPtAncestor;
                                GetWindowRect(hPointWnd, &rtPointWnd);
                                GetWindowRect(hClickAncestor, &rtPtAncestor);
                                g_info("[TabTipSystemBase] Button Up and not in edit and have different ancestor, FocusEdit(0x%x GetAncestor:0x%x) Ancestor:0x%x RECT:(%d,%d,%d,%d), mouse click:0x%X(%d,%d) RECT:(%d,%d,%d,%d) Ancestor:0x%x RECT:(%d,%d,%d,%d)",
                                    CTabTipSystemBase::m_hStaFocusEdit, ::GetAncestor(CTabTipSystemBase::m_hStaFocusEdit, GA_ROOT),
                                    hFocusAncestor, CTabTipSystemBase::m_rtStaFocusEdit.left, CTabTipSystemBase::m_rtStaFocusEdit.top,
                                    CTabTipSystemBase::m_rtStaFocusEdit.right, CTabTipSystemBase::m_rtStaFocusEdit.bottom,
                                    hPointWnd, pMouseHookStruct->pt.x, pMouseHookStruct->pt.y, rtPointWnd.left, rtPointWnd.top, rtPointWnd.right, rtPointWnd.bottom,
                                    hClickAncestor, rtPtAncestor.left, rtPtAncestor.top, rtPtAncestor.right, rtPtAncestor.bottom);

                                CTabTipSystemBase::m_hStaFocusEdit = NULL;
                                ::SetRectEmpty(&CTabTipSystemBase::m_rtStaFocusEdit);
                            }
                            break;
                        }
                    }
                    break;
                }
            }
            else  //  hPointWnd is NULL
            {
                if (PtInRect(&CTabTipSystemBase::m_rtStaFocusEdit, pMouseHookStruct->pt))
                {
                    g_info("[TabTipSystemBase] Cursor point window HWnd is NULL, Buttom Up and find in edit Rect, FocusEdit RECT:(%d,%d,%d,%d), mouse point(%d,%d) ",
                        CTabTipSystemBase::m_rtStaFocusEdit.left, CTabTipSystemBase::m_rtStaFocusEdit.top, CTabTipSystemBase::m_rtStaFocusEdit.right,
                        CTabTipSystemBase::m_rtStaFocusEdit.bottom, pMouseHookStruct->pt.x, pMouseHookStruct->pt.y);
                    bShowInputWnd = true;
                    break;
                }
            }

            if (CTabTipSystemBase::m_bStaShowingInputWnd)
            {
                g_info("[TabTipSystemBase] Buttom Up And not in FocusEdit, will hide InputWnd. FocusEdit(0x%x) RECT:(%d,%d,%d,%d), mouse point 0x%x(%d,%d) ",
                    CTabTipSystemBase::m_hStaFocusEdit, CTabTipSystemBase::m_rtStaFocusEdit.left, CTabTipSystemBase::m_rtStaFocusEdit.top,
                    CTabTipSystemBase::m_rtStaFocusEdit.right, CTabTipSystemBase::m_rtStaFocusEdit.bottom, hPointWnd, pMouseHookStruct->pt.x, pMouseHookStruct->pt.y);
            }
        } while (FALSE);

        if (CTabTipSystemBase::m_bStaShowingInputWnd) // show now
        {
            if (!bShowInputWnd) // need hide
            {
                g_info("[TabTipSystemBase] Will Post Hide InputWnd Msg");
                if (CTabTipSystemBase::ms_pIPCWndClient)
                {
                    CTabTipSystemBase::ms_pIPCWndClient->post_message(WM_TABTIP_DISPLAYINPUTWND, SW_HIDE, 0);
                }
            }
        }
        else // hide now
        {
            if (bShowInputWnd) // need show
            {
                if (CTabTipSystemBase::ms_pIPCWndClient)
                {
                    CTabTipSystemBase::ms_pIPCWndClient->post_message(WM_TABTIP_DISPLAYINPUTWND, SW_SHOW, 0);
                }
            }
        }
    }
    return CTabTipSystemBase::OnProcessIPCMsg(message, wParam, lParam);
}

LRESULT CALLBACK CTabTipSysServer2016::LowLevelMouseHookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    if ((wParam == WM_LBUTTONDOWN || wParam == WM_RBUTTONDOWN))
    {
        CTabTipSystemBase::m_bStaStartListenMouseUp = true;
    }
    else if (CTabTipSystemBase::m_bStaStartListenMouseUp && (wParam == WM_LBUTTONUP || wParam == WM_RBUTTONUP) && lParam) // 0x201 - 0x209 wParam & MK_LBUTTON
    {
        PMSLLHOOKSTRUCT pMouseHook = new MSLLHOOKSTRUCT{};
        memcpy(pMouseHook, (void*)lParam, sizeof(MSLLHOOKSTRUCT));
        if (CTabTipSystemBase::ms_pIPCWndClient)
        {
            CTabTipSystemBase::ms_pIPCWndClient->post_message(WM_TABTIP_MOUSELRBUTTONUP, 
                (WPARAM)WindowFromPoint(pMouseHook->pt), (LPARAM)pMouseHook);
        }
    }

    return CallNextHookEx(m_hStaMouseHook, nCode, wParam, lParam);//下一个钩子
}

BOOL CTabTipSysServer2016::Initialize()
{
#ifndef _WIN64  //只在win64下系统起作用 tiptsf.dll是64位的
    return FALSE;
#endif
    DWORD pid = GetCurrentProcessId();
    DWORD dwSessionId = 0;
    ProcessIdToSessionId(pid, &dwSessionId);
    wsprintf(m_szBufTipSharedMemoryName, L"%s%d%s", L"Local\\", dwSessionId, DefaultTipSharedMemoryName);

    m_hShareMemoryFileMap = OpenFileMappingW(FILE_MAP_WRITE | FILE_MAP_READ, 0, m_szBufTipSharedMemoryName);
    if (!m_hShareMemoryFileMap)
    {
        m_hShareMemoryFileMap = CreateFileMappingW((HANDLE)-1, NULL, PAGE_READWRITE, 0, 0x68u, m_szBufTipSharedMemoryName);
    }
    if (m_hShareMemoryFileMap)
    {
        LPVOID lpViewFile = MapViewOfFile(m_hShareMemoryFileMap, FILE_MAP_WRITE | FILE_MAP_READ, 0, 0, 0);
        if (lpViewFile)
        {
            if (*(DWORD*)((__int64)lpViewFile + 44) == FALSE)
            {
                *(DWORD*)((__int64)lpViewFile + 44) = 0x01;
            }
        }
    }

    WCHAR szTiptsfDllPath[MAX_PATH] = { 0 };
    do
    {
        if (m_hTipTsfModule)
            break;
        HMODULE hMod = GetModuleHandle(NULL);
        if (NULL == hMod)
        {
            break;
        }
        wchar_t szPath[MAX_PATH] = { 0 };
        if (NULL == GetModuleFileNameW(hMod, szPath, MAX_PATH))
        {
            break;
        }
        wchar_t* p = wcsrchr(szPath, L'\\');
        if (NULL == p)
        {
            break;
        }
        *(++p) = '\0';
        swprintf_s(szTiptsfDllPath, MAX_PATH, L"%s" TIPTSFDLL, szPath);
        m_hTipTsfModule = LoadLibraryW(szTiptsfDllPath);
    } while (FALSE);

    BOOL bCallModuleOk = FALSE;
    do
    {
        if (!m_hTipTsfModule)
        {
            break;
        }
        AdviseHook AdviseHook_tiptsf = (AdviseHook)((__int64)m_hTipTsfModule + 0xE510);
        StartCaretTracking StartCaretTracking_tiptsf = (StartCaretTracking)((__int64)m_hTipTsfModule + 0xEBB0);
        if (!AdviseHook_tiptsf(NULL, false))
        {
            break;
        }
        if (0 != StartCaretTracking_tiptsf(L"IPTip", true, false))
        {
            break;
        }
        bCallModuleOk = TRUE;
        m_bCaretTracking = true;
    } while (FALSE);
    return bCallModuleOk;
}

BOOL CTabTipSysServer2016::Uninitialize()
{
#ifndef _WIN64  //只在win64下系统起作用 tiptsf.dll是64位的
    return FALSE;
#endif

    BOOL bRet = FALSE;
    if (m_hShareMemoryFileMap)
    {
        CloseHandle(m_hShareMemoryFileMap);
        m_hShareMemoryFileMap = NULL;
    }

    do
    {
        if (!m_bCaretTracking)
        {
            break;
        }
        EndCaretTracking EndCaretTracking_tiptsf = (EndCaretTracking)((__int64)m_hTipTsfModule + 0xEEA0);
        UnadviseHook UnadviseHook_tiptsf = (UnadviseHook)((__int64)m_hTipTsfModule + 0xEE10);
        if (0 == EndCaretTracking_tiptsf(L"IPTip")
            && TRUE == UnadviseHook_tiptsf())// 好像无效
        {
            bRet = TRUE;
            m_bCaretTracking = false;
        }
    } while (FALSE);
    return bRet;
}